import math
import os
import random

import numpy
import openpyxl
from sklearn import preprocessing

random.seed(0)
dataPath = ''


def read_excel(name):
	wb = openpyxl.load_workbook(name + '.xlsx')
	sheet = wb.active
	res = list(sheet.rows)
	rdata = []
	for i in range(sheet.max_row - 1):
		rdata.append([i.value for i in res[i + 1]][0:9])
	return numpy.array(rdata)


def read_data(dir_str):
	'''
	读取txt文件中的数据
	数据内容：科学计数法保存的多行多列数据
	输入：txt文件的路径
	输出：小数格式的数组，行列与txt文件中相同
	'''
	data_temp = []
	with open(dir_str) as fdata:
		while True:
			line = fdata.readline()
			if not line:
				break
			data_temp.append([float(i) for i in line.split()])
	return numpy.array(data_temp)


def randome_init_train_test(data, n_tr):
	''' 随机划分训练集和测试集 '''
	# sklearn提供一个将数据集切分成训练集和测试集的函数train_test_split
	train_index = numpy.random.choice(data.shape[0], size=n_tr, replace=False, p=None)
	train_data = data[train_index]
	test_index = numpy.delete(numpy.arange(data.shape[0]), train_index)  # 删除train_index对应索引的行数
	test_data = data[test_index]
	return train_data, test_data


def min_max_normalization(np_array):
	''' 离差标准化，(Xi-min(X))/(max(X)-min(X)) '''
	min_max_scaler = preprocessing.MinMaxScaler()
	ret = min_max_scaler.fit_transform(np_array)
	return ret


def label_to_value(label):
	''' 标签转换为对应输出值 (由于输出层结构，需要修改输出数据结构)'''
	switch = {
		0.0: [1, 0, 0, 0],
		1.0: [0, 1, 0, 0],
		2.0: [0, 0, 1, 0],
		3.0: [0, 0, 0, 1]
	}
	return switch[label]


def value_to_label(value):
	''' 神经网络输出值转换为对应标签 '''
	return value.index(max(value))


def rand(min, max):
	''' 随机取[a, b]范围内的值 '''
	return (max - min) * random.random() + min


def make_matrix(m, n, fill=0.0):  # 生成多维矩阵
	mat = []
	for i in range(m):
		mat.append([fill] * n)
	return mat


def sigmoid(x):
	if x < -709:
		return 1.0 / (1.0 + math.exp(709))
	return 1.0 / (1.0 + math.exp(-x))


def sigmoid_derivative(x):
	return x * (1 - x)


class BPNeuralNetwork:
	def __init__(self):  # 设置在BP神经网络中用到的参数
		self.input_n = 0
		self.hidden_n = 0
		self.output_n = 0
		self.input_values = []  # [1.0] * self.input_n
		self.hidden_values = []  # [1.0] * self.hidden_n
		self.output_values = []  # [1.0] * self.output_n
		self.input_weights = []
		self.output_weights = []
		self.input_correction = []  # dw1
		self.output_correction = []  # dw2
		self.input_bias = []
		self.output_bias = []
		self.data_mode = []
		self.test_data = []

	def setup(self, ni, nh, no):  # 参数设置
		self.input_n = ni

		self.hidden_n = nh
		self.output_n = no
		# init
		self.input_values = [1.0] * self.input_n  # 输入层神经元输出（输入特征）
		self.hidden_values = [1.0] * self.hidden_n  # 中间层神经元输出
		self.output_values = [1.0] * self.output_n  # 隐藏层神经元输出（预测结果）

	def readdata(self):
		if os.path.exists(dataPath + '/input_weights.txt') and os.path.exists(
				dataPath + '/output_weights.txt') and os.path.exists(
			dataPath + '/input_correction.txt') and os.path.exists(
			dataPath + '/output_correction.txt') and os.path.exists(
			dataPath + '/input_bias.txt') and os.path.exists(
			dataPath + '/output_bias.txt' and os.path.exists(
				dataPath + '/data_mode.txt') and os.path.exists(
				dataPath + '/test.xlsx')
		):
			self.input_weights = read_data(dataPath + '/input_weights.txt')
			self.output_weights = read_data(dataPath + '/output_weights.txt')
			self.input_correction = read_data(dataPath + '/input_correction.txt')
			self.output_correction = read_data(dataPath + '/output_correction.txt')
			self.input_bias = read_data(dataPath + '/input_bias.txt')
			self.output_bias = read_data(dataPath + '/output_bias.txt')
			self.data_mode = read_data(dataPath + '/data_mode.txt')
			self.test_data = read_excel(dataPath + '/test')[:, 1:]
			print('已读取数据文件')
			return True
		else:
			print('未能读取数据文件')
			return False

	def predict(self, inputs):  # 前向传播（在train中套在反向传播的train前面）
		# 输入层计算
		for i in range(self.input_n - 1):
			self.input_values[i] = inputs[i]
		# 隐藏层计算
		for j in range(self.hidden_n):
			total = 0.0
			for i in range(self.input_n):
				total += self.input_values[i] * self.input_weights[i][j]
			self.hidden_values[j] = sigmoid(total + self.input_bias[j])
		# 输出层计算
		for k in range(self.output_n):
			total = 0.0
			for j in range(self.hidden_n):
				total += self.hidden_values[j] * self.output_weights[j][k]
			self.output_values[k] = sigmoid(total + self.output_bias[k])
		return self.output_values[:]

	def back_propagate(self, case, label, learn, correct):
		# 前向预测
		self.predict(case)
		# 计算输出层的误差 w2
		output_deltas = [0.0] * self.output_n
		for o in range(self.output_n):
			error = label[o] - self.output_values[o]
			output_deltas[o] = sigmoid_derivative(self.output_values[o]) * error
		# 计算隐藏层的误差 w1
		hidden_deltas = [0.0] * self.hidden_n
		for h in range(self.hidden_n):
			error = 0.0
			for o in range(self.output_n):
				error += output_deltas[o] * self.output_weights[h][o]
			hidden_deltas[h] = sigmoid_derivative(self.hidden_values[h]) * error
		# 更新隐藏-输出层权重 b2
		for h in range(self.hidden_n):
			for o in range(self.output_n):
				change = output_deltas[o] * self.hidden_values[h]
				self.output_weights[h][o] += learn * change + correct * self.output_correction[h][o]
				self.output_correction[h][o] = change
				self.output_bias[o] += learn * change
		# 更新输入-隐藏层权重 b1
		for i in range(self.input_n):
			for h in range(self.hidden_n):
				change = hidden_deltas[h] * self.input_values[i]
				self.input_weights[i][h] += learn * change + correct * self.input_correction[i][h]
				self.input_correction[i][h] = change
				self.input_bias[i] += learn * change

		# 计算样本的均方误差
		error = 0.0
		for o in range(len(label)):
			error += 0.5 * (label[o] - self.output_values[o]) ** 2
		return error

# def train(self, datas, labels, epochs=5000, learn=0.05, correct=0.1, stop_error=0.001):
# 	for j in range(epochs):
# 		error = 0.0
# 		for i in range(len(datas)):
# 			label = labels[i]
# 			data = datas[i]
# 			error += self.back_propagate(data, label, learn, correct)
# 		if error <= stop_error:
# 			return j + 1
# 		print(f"当前准确率：{accuracy} 第{times}轮第{j}次:error={error}")
# 	return epochs
